package org.g4studio.core.mvc.xstruts.chain.commands;

import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.CatalogFactory;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.Filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.g4studio.core.mvc.xstruts.chain.contexts.ActionContext;

/**
 * <p>
 * Intercept any exception thrown by a subsequent <code>Command</code> in this
 * processing chain, and fire the configured exception handler chain after
 * storing the exception that has occurred into the <code>Context</code>.
 * </p>
 * 
 * @version $Rev: 421119 $ $Date: 2005-11-12 13:01:44 -0500 (Sat, 12 Nov 2005) $
 */
public class ExceptionCatcher extends ActionCommandBase implements Filter {
	/**
	 * <p>
	 * Provide Commons Logging instance for this class.
	 * </p>
	 */
	private static final Log LOG = LogFactory.getLog(ExceptionCatcher.class);

	// ------------------------------------------------------ Instance Variables

	/**
	 * <p>
	 * Field for CatalogName property.
	 * </p>
	 */
	private String catalogName = null;

	/**
	 * <p>
	 * Field for ExceptionCommand property.
	 * </p>
	 */
	private String exceptionCommand = null;

	// -------------------------------------------------------------- Properties

	/**
	 * <p>
	 * Return the name of the <code>Catalog</code> in which to perform lookups,
	 * or <code>null</code> for the default <code>Catalog</code>.
	 * </p>
	 * 
	 * @return Name of catalog to use, or null
	 */
	public String getCatalogName() {
		return (this.catalogName);
	}

	/**
	 * <p>
	 * Set the name of the <code>Catalog</code> in which to perform lookups, or
	 * <code>null</code> for the default <code>Catalog</code>.
	 * </p>
	 * 
	 * @param catalogName
	 *            The new catalog name or <code>null</code>
	 */
	public void setCatalogName(String catalogName) {
		this.catalogName = catalogName;
	}

	/**
	 * <p>
	 * Return the name of the command to be executed if an exception occurs.
	 * </p>
	 * 
	 * @return The name of the command to be executed on an exception
	 */
	public String getExceptionCommand() {
		return (this.exceptionCommand);
	}

	/**
	 * <p>
	 * Set the name of the command to be executed if an exception occurs.
	 * </p>
	 * 
	 * @param exceptionCommand
	 *            The name of the chain to be executed
	 */
	public void setExceptionCommand(String exceptionCommand) {
		this.exceptionCommand = exceptionCommand;
	}

	// ---------------------------------------------------------- Public Methods

	/**
	 * <p>
	 * Clear any existing stored exception and pass the <code>context</code> on
	 * to the remainder of the current chain.
	 * </p>
	 * 
	 * @param actionCtx
	 *            The <code>Context</code> for the current request
	 * @return <code>false</code> so that processing continues
	 * @throws Exception
	 *             On any error
	 */
	public boolean execute(ActionContext actionCtx) throws Exception {
		actionCtx.setException(null);

		return (false);
	}

	/**
	 * <p>
	 * If an exception was thrown by a subsequent <code>Command</code>, pass it
	 * on to the specified exception handling chain. Otherwise, do nothing.
	 * </p>
	 * 
	 * @param context
	 *            The {@link Context} to be processed by this {@link Filter}
	 * @param exception
	 *            The <code>Exception</code> (if any) that was thrown by the
	 *            last {@link Command} that was executed; otherwise
	 *            <code>null</code>
	 * @return TRUE if post processing an exception occurred and the exception
	 *         processing chain invoked
	 * @throws IllegalStateException
	 *             If exception throws exception
	 */
	public boolean postprocess(Context context, Exception exception) {
		// Do nothing if there was no exception thrown
		if (exception == null) {
			return (false);
		}

		// Stash the exception in the specified context attribute
		if (LOG.isDebugEnabled()) {
			LOG.debug("Attempting to handle a thrown exception");
		}

		ActionContext actionCtx = (ActionContext) context;

		actionCtx.setException(exception);

		// Execute the specified command
		try {
			Command command = lookupExceptionCommand();

			if (command == null) {
				LOG.error("Cannot find exceptionCommand '" + exceptionCommand + "'");
				throw new IllegalStateException("Cannot find exceptionCommand '" + exceptionCommand + "'");
			}

			if (LOG.isTraceEnabled()) {
				LOG.trace("Calling exceptionCommand '" + exceptionCommand + "'");
			}

			command.execute(context);
		} catch (Exception e) {
			LOG.warn("Exception from exceptionCommand '" + exceptionCommand + "'", e);
			throw new IllegalStateException("Exception chain threw exception");
		}

		return (true);
	}

	/**
	 * <p>
	 * Return the command to be executed if an exception occurs.
	 * </p>
	 * 
	 * @return The command to be executed if an exception occurs
	 * @throws IllegalArgumentException
	 *             If catalog cannot be found
	 * @throws IllegalStateException
	 *             If command property is not specified
	 */
	protected Command lookupExceptionCommand() {
		String catalogName = getCatalogName();
		Catalog catalog;

		if (catalogName == null) {
			catalog = CatalogFactory.getInstance().getCatalog();

			if (catalog == null) {
				LOG.error("Cannot find default catalog");
				throw new IllegalArgumentException("Cannot find default catalog");
			}
		} else {
			catalog = CatalogFactory.getInstance().getCatalog(catalogName);

			if (catalog == null) {
				LOG.error("Cannot find catalog '" + catalogName + "'");
				throw new IllegalArgumentException("Cannot find catalog '" + catalogName + "'");
			}
		}

		String exceptionCommand = getExceptionCommand();

		if (exceptionCommand == null) {
			LOG.error("No exceptionCommand property specified");
			throw new IllegalStateException("No exceptionCommand property specfied");
		}

		return catalog.getCommand(exceptionCommand);
	}
}
